﻿package mlc.avm2.visualizing;
import flash.display.DisplayObject;
import flash.display.Stage;
import flash.events.Event;
import flash.Lib;
import hsl.avm2.translating.AVM2Signaler;
import hsl.avm2.translating.CustomPriorityAVM2Signaler;
import hsl.haxe.data.keyboard.KeyCombination;
import hsl.haxe.data.mathematics.Point;
import hsl.haxe.Signal;
import mlc.avm2.visualizing.writetarget.BufferWriteTarget;
import mlc.avm2.visualizing.writetarget.NullWriteTarget;
import mlc.avm2.visualizing.writetarget.WriteTarget;
import mlc.haxe.input.ConsoleInputSource;
import mlc.haxe.output.ConsoleOutputHandler;
using hsl.avm2.plugins.KeyboardShortcuts;

/**
 * A console visualizer, for AVM2 websites, applications and games. This class provides a visual representation of the console.
 * This visual representation can appear right away, or appears when the user presses the tilde key while holding the control
 * key down, or the C key while holding both the control and the shift key down. This visual representation adds itself to the
 * stage right away, or when the user uses such a shortcut, depending on the configuration.
 */
class AVM2ConsoleVisualizer {
	/**
	 * The more accurately typed output handler.
	 */
	private var _outputHandler:AVM2ConsoleVisualizerOutputHandler;
	/**
	 * The input source. Passing this object to the constructor of the console wires the input field of the visualizer to the
	 * console. This allows the user to provide input for the console through the visual representation.
	 */
	public var inputSource(default, null):ConsoleInputSource;
	/**
	 * The output handler. Passing this object to the constructor of the console wires the output field of the visualizer to the
	 * console. This will put the output of the console in the output field of the visual representation.
	 */
	public var outputHandler(default, null):ConsoleOutputHandler;
	/**
	 * A quick reference to the stage.
	 */
	private var stage:Stage;
	/**
	 * A signaler that dispatches signals when something is added to the stage. This signaler is constructed when the view is
	 * first opened.
	 */
	private var stageGainedChildSignaler:AVM2Signaler<Void>;
	/**
	 * A signaler that dispatches signals when the stage resizes. This signaler is constructed when the view is first opened.
	 */
	private var stageResizedSignaler:AVM2Signaler<Void>;
	/**
	 * The view that is used in this visualizer.
	 */
	private var view:AVM2ConsoleVisualizerView;
	/**
	 * Creates a new AVM2 console visualizer. The original stage dimensions should be passed. If they are not passed, they will
	 * be guessed. Such guessed values can be wrong, and wrong values could result in visual glitches. If you set the
	 * initiallyOpened argument to true, the console will be opened right away. If you set that argument to false (the default),
	 * the user can open the console by pressing the tilde key while holding the control key down, or the C key while holding
	 * both the control and the shift key down. You can choose how the output that comes from the console before the visual
	 * representation has been opened is handled. If you set the write to buffer argument to true (the default), such output will
	 * be stored in a buffer and will appear in the visual representation when it first opens. If you set that property to false,
	 * such output will be ignored: the user will have to open the visual representation before output from the console appears
	 * in it.
	 */
	public function new(?originalStageDimensions:Point, initiallyOpened:Bool = false, writeToBuffer:Bool = true):Void {
		// Store a reference to the stage, for easy access.
		stage = Lib.current.stage;
		// Create the view, which is also the input source.
		inputSource = view = new AVM2ConsoleVisualizerView(new AVM2ConsoleVisualizerViewRepositioner(stage, originalStageDimensions), stage);
		// Choose the initial write target for the output handler.
		var initialWriteTarget:WriteTarget =
			untyped if (writeToBuffer) {
				new BufferWriteTarget(view);
			} else {
				new NullWriteTarget();
			}
		// Create the output handler.
		outputHandler = _outputHandler = new AVM2ConsoleVisualizerOutputHandler(initialWriteTarget);
		// Toggle whether the view is opened or not, based on certain key combinations.
		stage.getKeyPressedDownSignaler().bind(toggleViewOpened);
		// When the view initializes, wire it with the stage.
		view.initializedSignaler.bindVoid(wireStageWithView);
		
		if (initiallyOpened) {
			view.toggleOpened(stage);
		}
	}
	private function toggleViewOpened(keyCombination:KeyCombination):Void {
		// The tilde key (which has key code 192) is used to open or close the console, just like in Half-Life, Quake, Unreal,
		// Counter-Strike, Crysis and Oblivion. However, the control key must also be in a down state. Additionally, the console
		// can also be opened by pressing the C key (which has key code 67) while the user holds the control and the shift key
		// down.
		if ((192 == keyCombination.keyCode && keyCombination.modifierKeysState.controlKeyDown) || (67 == keyCombination.keyCode && keyCombination.modifierKeysState.controlKeyDown && keyCombination.modifierKeysState.shiftKeyDown)) {
			view.toggleOpened(stage);
		}
	}
	private function resizeView():Void {
		view.resize(stage.stageWidth, stage.stageHeight);
	}
	private function wireStageWithView():Void {
		// Create the signaler that reacts when the stage resizes.
		stageResizedSignaler = new AVM2Signaler<Void>(stage, stage, Event.RESIZE);
		// When the stage resizes, resize the view.
		stageResizedSignaler.bindVoid(resizeView);
		// Create the signaler that reacts whenever something is added to the stage.
		stageGainedChildSignaler = new CustomPriorityAVM2Signaler<Void>(stage, stage, Event.ADDED, -1 << 31);
		// Call the enforce highest index method whenever something new is added to the stage. This almost ensures that the console
		// is always on top.
		stageGainedChildSignaler.bindAdvanced(view.enforceHighestIndex);
		// Set the target of the output handler to the view, as the view can now accept writes.
		_outputHandler.target = view;
	}
}